//============================================================================
// Name        : MemoryPool.cpp
// Author      : Peter.Song
// Version     :
// Copyright   : Your copyright notice
// Description : Simple memory pool in C++, Ansi-style
//============================================================================
#include <assert.h>
#include "MemoryPool.h"
#include "Thread.h"
#include "xScopeLock.h"

/***********************************************
 *
 * Global Memory Pool
 *
 **********************************************/

CGlobalMemPool*CGlobalMemPool::mp_Instance = NULL;

/***************************************************
 * Destructor
 ***************************************************/
CGlobalMemPool::~CGlobalMemPool()
{
	pthread_mutex_destroy(mp_mutex);
	delete(mp_mutex);
	mp_mutex = NULL;

	size_t totalChunkCount = 0;
	size_t totalFreeChunkCount = 0;

	for(size_t i = 0; i < EN_BIN_INDEX_MAX; i++){
		m_allocInfo[i].clear();
		MemBlock* pMemBlock = mp_blockHeadArray[i];
		totalFreeChunkCount += m_freeChunkCountArray[i];
		while(pMemBlock){
			MemBlock* pMemBlockNext = pMemBlock->next;
			delete pMemBlock;
			pMemBlock = pMemBlockNext;
			totalChunkCount += BLOCK_CHUNK_SIZE(i);
		}
	}

	assert(totalChunkCount == totalFreeChunkCount); // logic memory leak in user program.
}

/***************************************************
 * Public scope
 ***************************************************/
void CGlobalMemPool::Apply(size_t index, npINTPOINTER* head, npINTPOINTER* tail, size_t* chunk_num, npINTPOINTER* local_pool_cursor)
{
	CXScopeLock scope_lock(mp_mutex);

	if(m_allocInfo[index].size() > 0){
		const ST_ALLOC_UNIT& alloc_unit = m_allocInfo[index].back();
		*head = (npINTPOINTER)mp_freeChunkHeadArray[index];
		*tail = (npINTPOINTER)alloc_unit.cursor;
		*local_pool_cursor = (npINTPOINTER)alloc_unit.local_pool_cursor;
		*chunk_num = alloc_unit.chunk_num;
		npINTPOINTER freeHeadAddr = *(npINTPOINTER*)(alloc_unit.cursor);
		mp_freeChunkHeadArray[index] = (void*)freeHeadAddr;
		m_freeChunkCountArray[index] -= alloc_unit.chunk_num;
		m_allocInfo[index].pop_back();
	}

}

void CGlobalMemPool::RecycleFreeChunks(size_t index, void* freeChunkHead, CURSOR cursor)
{
	CXScopeLock scope_lock(mp_mutex);

	*(npINTPOINTER*)cursor = (npINTPOINTER)mp_freeChunkHeadArray[index];
	mp_freeChunkHeadArray[index] = freeChunkHead;
	m_freeChunkCountArray[index] += BLOCK_CHUNK_SIZE(index);

	m_allocInfo[index].push_back(ST_ALLOC_UNIT(cursor, BLOCK_CHUNK_SIZE(index))); //local_pool_cursor is NULL
}

void CGlobalMemPool::RecycleLocalPool(void** freeChunkHeadArray,
		                               void** freeChunkTailArray,
		                               MemBlock** blockHeadArray,
		                               MemBlock** blockTailArray,
		                               CURSOR* cursor,
		                               size_t* freeChunkCountArray)
{
	CXScopeLock scope_lock(mp_mutex);

	for(size_t i = 0; i < EN_BIN_INDEX_MAX; i++){
		if(blockTailArray[i]){
			blockTailArray[i]->next = mp_blockHeadArray[i];
			mp_blockHeadArray[i] = blockHeadArray[i];
		}
		if(freeChunkCountArray[i] > 0){
			*(npINTPOINTER*)freeChunkTailArray[i] = (npINTPOINTER)mp_freeChunkHeadArray[i];
			mp_freeChunkHeadArray[i] = freeChunkHeadArray[i];
			m_freeChunkCountArray[i] += freeChunkCountArray[i];

			m_allocInfo[i].push_back(ST_ALLOC_UNIT(freeChunkTailArray[i], freeChunkCountArray[i], cursor[i]));
		}
	}
}

CGlobalMemPool* CGlobalMemPool::GetInstance()
{
	if(!mp_Instance){
		mp_Instance = new CGlobalMemPool;
	}
	return mp_Instance;
}

void CGlobalMemPool::ReleaseInstance()
{
	if(mp_Instance){
		delete mp_Instance;
		mp_Instance = NULL;
	}
}
/***************************************************
 * Private scope
 ***************************************************/
/***************************************************
 * Constructor
 ***************************************************/
CGlobalMemPool::CGlobalMemPool()
: mp_mutex(NULL)
{
	for(size_t i = 0; i < EN_BIN_INDEX_MAX; i++){
		mp_freeChunkHeadArray[i] = NULL;
		mp_blockHeadArray[i] = NULL;
		m_freeChunkCountArray[i] = 0;
		m_allocInfo[i].clear();
	}

	mp_mutex =  new pthread_mutex_t;
	pthread_mutex_init(mp_mutex, NULL);
}

/***********************************************
 *
 * Thread local Memory Pool
 *
 **********************************************/


/***************************************************
 * Constructor and Destructor
 ***************************************************/
CLocalMempool::CLocalMempool()
{
	for(size_t i = 0; i < EN_BIN_INDEX_MAX; i++){
		mp_freeChunkHeadArray[i] = NULL;
		mp_freeChunkTailArray[i] = NULL;
		mp_blockHeadArray[i] = NULL;
		mp_blockTailArray[i] = NULL;
		m_freeChunkCountArray[i] = 0;
		m_cursor[i] = NULL;
	}
}

CLocalMempool::~CLocalMempool()
{
	//provide free chunk list && block list to GP.
	CGlobalMemPool::GetInstance()->RecycleLocalPool(mp_freeChunkHeadArray, mp_freeChunkTailArray, mp_blockHeadArray, mp_blockTailArray, m_cursor, m_freeChunkCountArray);
	for(size_t i = 0; i < EN_BIN_INDEX_MAX; i++){
		mp_freeChunkHeadArray[i] = NULL;
		mp_freeChunkTailArray[i] = NULL;
		mp_blockHeadArray[i] = NULL;
		mp_blockTailArray[i] = NULL;
		m_freeChunkCountArray[i] = 0;
		m_cursor[i] = NULL;
	}
}

/***************************************************
 * Public scope
 ***************************************************/
void* CLocalMempool::New(std::size_t size)
{
	size_t index = indexMapper(size);

	if(!mp_freeChunkHeadArray[index]){
		npINTPOINTER head = 0;
		npINTPOINTER tail = 0;
		size_t chunk_num = 0;
		npINTPOINTER cursor = 0;

		CGlobalMemPool::GetInstance()->Apply(index, &head, &tail, &chunk_num, &cursor); //allocate from GP
		if(chunk_num){// GP has alloc unit
			if(cursor){
				m_cursor[index] = (CURSOR)cursor;
			}
		}
		else{ //GP has no alloc unit
			MemBlock* pMemBlock = new MemBlock;
			memset(pMemBlock->data, '\0', BLOCK_SIZE);
			chunk_num = BLOCK_CHUNK_SIZE(index); // chunk number the block contains

			pMemBlock->next = mp_blockHeadArray[index];
			mp_blockHeadArray[index] = pMemBlock;
			if(!mp_blockTailArray[index]){
				mp_blockTailArray[index] = pMemBlock;
			}

			head = (npINTPOINTER)mp_blockHeadArray[index]->data;

			for(size_t i = 0; i < chunk_num -1; i++){
				npINTPOINTER* pChunk = (npINTPOINTER*)(mp_blockHeadArray[index]->data + i * BIN_SIZE[index]);
				*pChunk = (npINTPOINTER)(mp_blockHeadArray[index]->data + (i+1) * BIN_SIZE[index]);
			}

			tail = (npINTPOINTER)(mp_blockHeadArray[index]->data + ((chunk_num -1) * BIN_SIZE[index]));
		}

		mp_freeChunkHeadArray[index] = (void*)head;
		mp_freeChunkTailArray[index] = (void*)tail;
		m_freeChunkCountArray[index] += chunk_num;
		*(npINTPOINTER*)tail = 0;
	}

	void* pAllocated = mp_freeChunkHeadArray[index];
	npINTPOINTER chunkHeadNext = *(npINTPOINTER*)mp_freeChunkHeadArray[index];
	mp_freeChunkHeadArray[index] = (void*)chunkHeadNext;
	m_freeChunkCountArray[index]--;

	if(m_freeChunkCountArray[index] == BLOCK_CHUNK_SIZE(index)){
		m_cursor[index] = NULL;
	}

	return pAllocated;
}

void CLocalMempool::Delete(void* ptrObj, std::size_t size)
{

	size_t index = indexMapper(size);

	*(npINTPOINTER*)ptrObj = (npINTPOINTER)mp_freeChunkHeadArray[index];

	if(!mp_freeChunkHeadArray[index]){
		mp_freeChunkTailArray[index] = ptrObj; // Update tail if free chunk list is empty
	}

	mp_freeChunkHeadArray[index] = ptrObj;

	m_freeChunkCountArray[index]++;

	if(m_freeChunkCountArray[index] == BLOCK_CHUNK_SIZE(index) + 1){
		m_cursor[index] = mp_freeChunkHeadArray[index];
	}

	size_t freeChunkThreshold = 2 * BLOCK_CHUNK_SIZE(index);

	if(m_freeChunkCountArray[index] >= freeChunkThreshold){ // avoid drift, recycle a block size free chunks to global for reuse.
		npINTPOINTER freeChunkHeadNext = *(npINTPOINTER*)m_cursor[index];
		m_freeChunkCountArray[index] = m_freeChunkCountArray[index] - BLOCK_CHUNK_SIZE(index);
		CGlobalMemPool::GetInstance()->RecycleFreeChunks(index, mp_freeChunkHeadArray[index], m_cursor[index]);
		mp_freeChunkHeadArray[index] = (void*)freeChunkHeadNext; // the new chunk head for local pool (size == BLOCK_CHUNK_SIZE(index))
		m_cursor[index] = NULL;
	}

}
/***************************************************
 * Private scope
 ***************************************************/
size_t CLocalMempool::indexMapper(std::size_t size)
{
	size_t index = EN_BIN_INDEX_MAX;

	if(size <= BIN_SIZE[EN_BIN4]){
		index = EN_BIN4;
	} else if(size > BIN_SIZE[EN_BIN4] && size <= BIN_SIZE[EN_BIN8]) {
		index = EN_BIN8;
	} else if(size > BIN_SIZE[EN_BIN8] && size <= BIN_SIZE[EN_BIN16]) {
		index = EN_BIN16;
	} else if(size > BIN_SIZE[EN_BIN16] && size <= BIN_SIZE[EN_BIN32]) {
		index = EN_BIN32;
	} else if(size > BIN_SIZE[EN_BIN32] && size <= BIN_SIZE[EN_BIN64]) {
		index = EN_BIN64;
	} else if(size > BIN_SIZE[EN_BIN64] && size <= BIN_SIZE[EN_BIN128]) {
		index = EN_BIN128;
	} else{
		index = EN_BIN256;
	}

	return index;
}
